/*
 * Licensed under the MIT License
 * 
 * Copyright (c) 2011 Kay Kreuning
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 * 
 * http://code.google.com/p/hyves-as3-sdk/
 */
package nl.hyves.api.core
{
	import nl.hyves.api.parameters.IParameters;
	import nl.hyves.api.login.ILogin;
	import nl.hyves.api.net.HyvesRequest;
	import nl.hyves.api.net.HyvesUpload;
	import nl.hyves.api.net.IHyvesRequest;
	import nl.hyves.api.utils.AuthUtil;
	import nl.hyves.api.utils.BatchCall;
	import nl.hyves.api.utils.TokenStorage;

	import com.adobe.cairngorm.contract.Contract;

	public class HyvesAPI implements IHyvesAPI
	{
		protected static const ENDPOINT:String = 'http://data.hyves-api.nl/';
		protected var _consumer:Consumer;
		protected var storage:TokenStorage;
		protected var queue:Vector.<IHyvesRequest>;
		protected var _accesstoken:OAuthToken;
		protected var onAuthenticated:Function;

		/**
		 * @inheritDoc
		 */
		public function get endpoint():String
		{
			return ENDPOINT;
		}

		/**
		 * @inheritDoc
		 */
		public function get consumer():Consumer
		{
			return _consumer;
		}

		/**
		 * @inheritDoc
		 */
		public function get accesstoken():OAuthToken
		{
			return _accesstoken;
		}

		/**
		 * Constructor.
		 * 
		 * @param consumerKey consumer key provided by hyves.nl for this application
		 * @param consumerSecret consumer secret provided by hyves.nl for this application
		 * @param useCaching if false, this HyvesAPI instace will not use the cache and needs authentication every time it runs
		 */
		public function HyvesAPI(consumerKey:String, consumerSecret:String, useCaching:Boolean = true):void
		{
			Contract.precondition(consumerKey && consumerKey != '');
			Contract.precondition(consumerSecret && consumerSecret != '');

			_consumer = new Consumer(consumerKey, consumerSecret);

			storage = new TokenStorage(consumer);
			queue = new Vector.<IHyvesRequest>();

			if (useCaching && storage.has())
			{
				_accesstoken = storage.get();
			}

			Contract.postcondition(consumer != null);
			Contract.postcondition(storage != null);
			Contract.postcondition(queue != null);
		}

		/**
		 * @inheritDoc
		 */
		public function isLoggedIn(forAPIMethods:Vector.<String> = null):Boolean
		{
			if (accesstoken && accesstoken.methods)
			{
				// Check to see if the current token has te access to the necessary methods:
				if (forAPIMethods)
				{
					var tokenMethods:Array = accesstoken.methods.split(',');

					for each (var method:String in forAPIMethods)
					{
						if (tokenMethods.indexOf(method) < 0)
						{
							return false;
						}
					}
				}

				return accesstoken.oauth_token && accesstoken.oauth_token_secret && accesstoken.userid;
			}

			return false;
		}

		/**
		 * @inheritDoc
		 */
		public function login(method:ILogin, callback:Function):void
		{
			Contract.precondition(method != null);
			Contract.precondition(callback != null);

			onAuthenticated = callback;

			_accesstoken = null;

			Contract.postcondition(onAccesstoken != null);

			method.authenticate(this, onAccesstoken);
		}

		/**
		 * @inheritDoc
		 */
		public function clearAccesstokenCache():void
		{
			_accesstoken = null;

			storage.clear();

			Contract.postcondition(accesstoken == null);
		}

		/**
		 * @inheritDoc
		 */
		public function call(method:*, callback:Function, ...parameters):void
		{
			Contract.precondition(method != null);
			Contract.precondition(callback != null);
			
			var requestParameters:Object = buildParameters(parameters);
			
			var immediate:Boolean = false;
			
			// Check to see if the method is set to execute immediately:
			if (requestParameters && requestParameters.hasOwnProperty('api_immediate'))
			{
				immediate = requestParameters.api_immediate;
				
				// Remove the api_immediate parameter or it will cause trouble with the request:
				delete requestParameters.api_immediate;
			}
			
			var request:IHyvesRequest = new HyvesRequest(this, AuthUtil.buildParameters(method, requestParameters), callback);
			
			Contract.postcondition(request != null);

			load(request, immediate);
		}

		/**
		 * @inheritDoc
		 */
		public function upload(image:*, callback:Function, parameters:Object = null):void
		{
			Contract.precondition(image != null);
			Contract.precondition(callback != null);

			var request:IHyvesRequest = new HyvesUpload(this, image, callback, parameters);

			Contract.postcondition(request != null);

			load(request);
		}

		/**
		 * @inheritDoc
		 */
		public function createBatch():BatchCall
		{
			return new BatchCall(this);
		}
		
		private function load(request:IHyvesRequest, immediate:Boolean = false):void
		{
			(isLoggedIn() || immediate) ? request.load() : queue.push(request);
		}

		private function onAccesstoken(accesstoken:OAuthToken):void
		{
			_accesstoken = accesstoken;

			if (isLoggedIn())
			{
				// Check if there is anything on queue and if so process the queue:
				if (queue.length)
				{
					for (var i:int = 0; i < queue.length; i++)
					{
						queue[i].load();
					}

					// Clear the queue again:
					queue = new Vector.<IHyvesRequest>();
				}

				// Save the latest accesstoken:
				storage.set(accesstoken);

				onAuthenticated();
			}
		}
		
		private function buildParameters(parameters:Array):Object
		{
			var result:Object = new Object();
			
			for (var i : int = 0; i < parameters.length; i++)
			{
				var currentParameters:Object = parameters[i];
				
				if (currentParameters is IParameters)
				{
					currentParameters = IParameters(currentParameters).parameters;
				}
				
				for (var key:String in currentParameters)
				{
					result[key] = currentParameters[key];
				}
				
			}
			
			return result;
		}
	}
}